<template>
  <view v-if="!disabled" class="tn-image-upload-class tn-image-upload">
    <movable-area
      class="tn-image-upload__movable-area"
      :style="{
        height: movableAreaHeight
      }"
      @mouseenter="mouseEnterArea"
      @mouseleave="mouseLeaveArea"
      v-if="show"
    >
      <block
        v-for="(item, index) in lists"
        :key="item.id"
      >
        <movable-view
          class="tn-image-upload__movable-view"
          :style="{
            width: $tn.string.getLengthUnitValue(width),
            height: $tn.string.getLengthUnitValue(height),
            zIndex: item.zIndex,
            opacity: item.opacity,
            
          }"
          :x="item.x"
          :y="item.y"
          direction="all"
          :damping="40"
          :disabled="item.disabled"
          @change="movableChange($event, item)"
          @touchstart="movableStart(item)"
          @mousedown="movableStart(item)"
          @touchend="movableEnd(item)"
          @mouseup="movableEnd(item)"
          @longpress="movableLongPress(item)"
        >
          <view
            class="tn-image-upload__item tn-image-upload__item-preview"
            :style="{
              width: $tn.string.getLengthUnitValue(itemWidth, 'px'),
              height: $tn.string.getLengthUnitValue(itemHeight, 'px'),
              transform: `scale(${item.scale})`
            }"
          >
            <!-- 删除按钮 -->
            <view
              v-if="deleteable"
              class="tn-image-upload__item-preview__delete"
              @tap.stop="deleteItem(index)"
              :style="{
                borderTopColor: deleteBackgroundColor
              }"
            >
              <view
                class="tn-image-upload__item-preview__delete--icon"
                :class="[`tn-icon-${deleteIcon}`]"
                :style="{
                  color: deleteColor
                }"
              ></view>
            </view>
            <!-- 进度条 -->
            <tn-line-progress
              v-if="showProgress && item.data.progress > 0 && !item.data.error"
              class="tn-image-upload__item-preview__progress"
              :percent="item.data.progress"
              :showPercent="false"
              :round="false"
              :height="8"
            ></tn-line-progress>
            <!-- 重试按钮 -->
            <view v-if="item.data.error" class="tn-image-upload__item-preview__error-btn" @tap.stop="retry(index)">点击重试</view>
            <!-- 图片信息 -->
            <image
              class="tn-image-upload__item-preview__image"
              :src="item.data.url || item.data.path"
              :mode="imageMode"
              @tap.stop="doPreviewImage(item.data.url || item.data.path, index)"
            ></image>
          </view>
        </movable-view>
      </block>
      
      <!-- 添加按钮 -->
      <view
        v-if="maxCount > lists.length"
        class="tn-image-upload__add"
        :style="{
          top: addBtn.y + 'px',
          left: addBtn.x + 'px',
          width: $tn.string.getLengthUnitValue(itemWidth, 'px'),
          height: $tn.string.getLengthUnitValue(itemHeight, 'px')
        }"
        @tap="selectFile"
      >
        <!-- 添加按钮 -->
        <view
          class="tn-image-upload__item tn-image-upload__item-add"
          hover-class="tn-hover-class"
          hover-stay-time="150"
          :style="{
            width: $tn.string.getLengthUnitValue(itemWidth, 'px'),
            height: $tn.string.getLengthUnitValue(itemHeight, 'px')
          }"
        >
          <view class="tn-image-upload__item-add--icon tn-icon-add"></view>
          <view class="tn-image-upload__item-add__tips">{{ uploadText }}</view>
        </view>
      </view>
    </movable-area>
  </view>
</template>

<script>
  export default {
    name: 'tn-image-upload-drag',
    props: {
      // 已上传的文件列表
      fileList: {
        type: Array,
        default() {
          return []
        }
      },
      // 上传图片地址
      action: {
        type: String,
        default: ''
      },
      // 上传文件的字段名称
      name: {
        type: String,
        default: 'file'
      },
      // 头部信息
      header: {
        type: Object,
        default() {
          return {}
        }
      },
      // 携带的参数
      formData: {
        type: Object,
        default() {
          return {}
        }
      },
      // 是否禁用
      disabled: {
        type: Boolean,
        default: false
      },
      // 是否自动上传
      autoUpload: {
        type: Boolean,
        default: true
      },
      // 最大上传数量
      maxCount: {
        type: Number,
        default: 9
      },
      // 预览上传图片的裁剪模式
      imageMode: {
        type: String,
        default: 'aspectFill'
      },
      // 点击图片是否全屏预览
      previewFullImage: {
        type: Boolean,
        default: true
      },
      // 是否显示进度条
      showProgress: {
        type: Boolean,
        default: true
      },
      // 是否显示删除按钮
      deleteable: {
        type: Boolean,
        default: true
      },
      // 删除按钮图标
      deleteIcon: {
        type: String,
        default: 'close'
      },
      // 删除按钮的背景颜色
      deleteBackgroundColor: {
        type: String,
        default: ''
      },
      // 删除按钮的颜色
      deleteColor: {
        type: String,
        default: ''
      },
      // 上传区域提示文字
      uploadText: {
        type: String,
        default: '选择图片'
      },
      // 显示toast提示
      showTips: {
        type: Boolean,
        default: true
      },
      // 预览图片和选择图片区域的宽度
      width: {
        type: Number,
        default: 200
      },
      // 预览图片和选择图片区域的高度
      height: {
        type: Number,
        default: 200
      },
      // 选择图片的尺寸
      // 参考上传文档 https://uniapp.dcloud.io/api/media/image
      sizeType: {
        type: Array,
        default() {
          return ['original', 'compressed']
        }
      },
      // 图片来源
      sourceType: {
        type: Array,
        default() {
          return ['album', 'camera']
        }
      },
      // 是否可以多选
      multiple: {
        type: Boolean,
        default: true
      },
      // 文件大小(byte)
      maxSize: {
        type: Number,
        default: 10 * 1024 * 1024
      },
      // 允许上传的类型
      limitType: {
        type: Array,
        default() {
          return ['png','jpg','jpeg','webp','gif','image']
        }
      },
      // 是否自定转换为json
      toJson: {
        type: Boolean,
        default: true
      },
      // 上传前钩子函数，每个文件上传前都会执行
      beforeUpload: {
        type: Function,
        default: null
      },
      // 删除文件前钩子函数
      beforeRemove: {
        type: Function,
        default: null
      },
      index: {
        type: [Number, String],
        default: ''
      }
    },
    computed: {
      movableAreaHeight() {
        if (this.lists.length < this.maxCount) {
          return Math.ceil((this.lists.length + 1) / this.baseData.columns) * uni.upx2px(this.height) + 'px'
        } else {
          return Math.ceil(this.lists.length / this.baseData.columns) * uni.upx2px(this.height) + 'px'
        }
      },
      itemWidth() {
        return uni.upx2px(this.width) - (uni.upx2px(10) * 2)
      },
      itemHeight() {
        return uni.upx2px(this.height) - (uni.upx2px(10) * 2)
      }
    },
    data() {
      return {
        lists: [],
        uploading: false,
        baseData: {
          windowWidth: 0,
          columns: 0,
          dragItem: null,
          widthPx: 0,
          heightPx: 0
        },
        addBtn: {
          x: 0,
          y: 0
        },
        timer: null,
        dragging: false,
        show:true
      }
    },
    watch: {
      // fileList: {
      //   handler(val) {
      //     val.map(value => {
      //       // 首先检查内部是否已经添加过这张图片，因为外部绑定了一个对象给fileList的话(对象引用)，进行修改外部fileList时，
      //       // 会触发watch，导致重新把原来的图片再次添加到this.lists
      //       // 数组的some方法意思是，只要数组元素有任意一个元素条件符合，就返回true，而另一个数组的every方法的意思是数组所有元素都符合条件才返回true
      //       let tmp = this.lists.some(listVal => {
      //         return listVal.url === value.url
      //       })
      //       // 如果内部没有这张图片，则添加到内部
      //       !tmp && this.lists.push({ url: value.url, error: false, progress: 100 })
      //     })
      //   },
      //   immediate: true
      // },
      lists(val) {
        this.$emit('on-list-change', this.sortList(), this.index)
      }
    },
    created() {
      this.baseData.windowWidth = uni.getSystemInfoSync().windowWidth
    },
    mounted() {
      this.$nextTick(() => {
        this.updateDragInfo()
      })
    },
    methods: {
      // 清除列表
      clear() {
        this.lists = []
        this.updateAddBtnPositioin()
      },
      // 重新上传队列中上传失败所有文件
      reUpload() {
        this.uploadFile()
      },
      // 选择图片
      selectFile() {
        if (this.disabled) return
        const {
          name = '',
          maxCount,
          multiple,
          maxSize,
          sizeType,
          lists,
          camera,
          compressed,
          sourceType
        } = this
        let chooseFile = null
        const newMaxCount = maxCount - lists.length
        // 只选择图片的时候使用 chooseImage 来实现
        chooseFile = new Promise((resolve, reject) => {
          uni.chooseImage({
            count: multiple ? (newMaxCount > 9 ? 9 : newMaxCount) : 1,
            sourceType,
            sizeType,
            success: resolve,
            fail: reject
          })
        })
        chooseFile.then(res => {
          let file = null
          let listOldLength = lists.length
          res.tempFiles.map((val, index) => {
            if (!this.checkFileExt(val)) return
            
            // 是否超出最大限制数量
            if (!multiple && index >= 1) return
            if (val.size > maxSize) {
              this.$emit('on-oversize', val, this.sortList(), this.index)
              this.showToast('超出可允许文件大小')
            } else {
              if (maxCount <= lists.length) {
                this.$emit('on-exceed', val, this.sortList(), this.index)
                this.showToast('超出最大允许的文件数')
                return
              }
              lists.push(this.handleDragListItem({
                url: val.path,
                progress: 0,
                error: false,
                file: val
              }))
              this.updateAddBtnPositioin()
            }
          })
          this.$emit('on-choose-complete', this.sortList(), this.index)
          if (this.autoUpload) this.uploadFile(listOldLength)
        }).catch(err => {
          this.$emit('on-choose-fail', err)
        })
      },
      // 提示用户信息
      showToast(message, force = false) {
        if (this.showTips || force) {
          this.$tn.message.toast(message)
        }
      },
      // 手动上传，通过ref进行调用
      upload() {
        this.uploadFile()
      },
      // 对失败图片进行再次上传
      retry(index) {
        this.lists[index].data.progress = 0
        this.lists[index].data.error = false
        this.lists[index].data.response = null
        this.$tn.message.loading('重新上传')
        this.uploadFile(index)
      },
      // 上传文件
      async uploadFile(index = 0) {
        if (this.disabled) return
        if (this.uploading) return
        // 全部上传完成
        if (index >= this.lists.length) {
          this.$emit('on-uploaded', this.sortList(), this.index)
          return
        }
        // 检查是否已经全部上传或者上传中
        if (this.lists[index].data.progress === 100) {
          this.lists[index].data.uploadTask = null
          if (this.autoUpload) this.uploadFile(index + 1)
          return
        }
        // 执行before-upload钩子
        if (this.beforeUpload && typeof(this.beforeUpload) === 'function') {
          // 在微信，支付宝等环境(H5正常)，会导致父组件定义的函数体中的this变成子组件的this
          // 通过bind()方法，绑定父组件的this，让this的this为父组件的上下文
          // 因为upload组件可能会被嵌套在其他组件内，比如tn-form，这时this.$parent其实为tn-form的this，
          // 非页面的this，所以这里需要往上历遍，一直寻找到最顶端的$parent，这里用了this.$u.$parent.call(this)
          let beforeResponse = this.beforeUpload.bind(this.$tn.$parent.call(this))(index, this.lists)
          // 判断是否返回了Promise
          if (!!beforeResponse && typeof beforeResponse.then === 'function') {
            await beforeResponse.then(res => {
              // promise返回成功，不进行操作继续
            }).catch(err => {
              // 进入catch回调的话，继续下一张
              return this.uploadFile(index + 1)
            })
          } else if (beforeResponse === false) {
            // 如果返回flase，继续下一张图片上传
            return this.uploadFile(index + 1)
          } else {
            // 为true的情况，不进行操作
          }
        }
        // 检查上传地址
        if (!this.action) {
          this.showToast('请配置上传地址', true)
          return
        }
        this.lists[index].data.error = false
        this.uploading = true
        // 创建上传对象
        const task = uni.uploadFile({
          url: this.action,
          filePath: this.lists[index].data.url,
          name: this.name,
          formData: this.formData,
          header: this.header,
          success: res => {
            // 判断啊是否为json字符串，将其转换为json格式
            let data = this.toJson && this.$tn.test.jsonString(res.data) ? JSON.parse(res.data) : res.data
            if (![200, 201, 204].includes(res.statusCode)) {
              this.uploadError(index, data)
            } else {
              this.lists[index].data.response = data
              this.lists[index].data.progress = 100
              this.lists[index].data.error = false
              this.$emit('on-success', data, index, this.sortList(), this.index)
            }
          },
          fail: err => {
            this.uploadError(index, err)
          },
          complete: res => {
            this.$tn.message.closeLoading()
            this.uploading = false
            this.uploadFile(index + 1)
            this.$emit('on-change', res, index, this.sortList(), this.index)
          }
        })
        this.lists[index].uploadTask = task
        task.onProgressUpdate(res => {
          if (res.progress > 0) {
            this.lists[index].data.progress = res.progress
            this.$emit('on-progress', res, index, this.sortList(), this.index)
          }
        })
      },
      // 上传失败
      uploadError(index, err) {
        this.lists[index].data.progress = 0
        this.lists[index].data.error = true
        this.lists[index].data.response = null
        this.showToast('上传失败，请重试')
        this.$emit('on-error', err, index, this.sortList(), this.index)
      },
      // 删除一个图片
      deleteItem(index) {
        if (!this.deleteable) return
        this.$tn.message.modal(
          '提示',
          '您确定要删除吗？',
          async () => {
            // 先检查是否有定义before-remove移除前钩子
            // 执行before-remove钩子
            if (this.beforeRemove && typeof(this.beforeRemove) === 'function') {
              let beforeResponse = this.beforeRemove.bind(this.$tn.$parent.call(this))(index, this.lists)
              // 判断是否返回promise 
              if (!!beforeResponse && typeof beforeResponse.then === 'function') {
                await beforeResponse.then(res => {
                  // promise返回成功不进行操作
                  this.handlerDeleteItem(index)
                }).catch(err => {
                  this.showToast('删除操作被中断')
                })
              } else if (beforeResponse === false) {
                this.showToast('删除操作被中断')
              } else {
                this.handlerDeleteItem(index)
              }
            } else {
              this.handlerDeleteItem(index)
            }
          }, true)
      },
      // 移除文件操作
      handlerDeleteItem(index) {
        // 如果文件正在上传中，终止上传任务
        if (this.lists[index].data.progress < 100 && this.lists[index].data.progress > 0) {
          typeof this.lists[index].data.uploadTask !== 'undefined' && this.lists[index].data.uploadTask.abort()
        }
        this.remove(index)
        this.$forceUpdate()
        this.$emit('on-remove', index, this.sortList(), this.index)
        this.showToast('删除成功')
      },
      // 移除文件，通过ref手动形式进行调用
      remove(index) {
        if (!this.deleteable) return
        // 判断索引合法
        if (index >= 0 && index < this.lists.length) {
          let currentItemIndex = this.lists[index].index
          this.lists.splice(index, 1)
          this.show = false;
          setTimeout(()=>{
            this.show = true;
            // 重新排列列表信息
            for (let item of this.lists) {
              if (item.index > currentItemIndex) {
                item.index -= 1
                item.x = item.positionX * this.baseData.widthPx
                item.y = item.positionY * this.baseData.heightPx
                item.positionX = item.index % this.baseData.columns
                item.positionY = Math.floor(item.index / this.baseData.columns)
                this.$nextTick(() => {
                  item.x = item.positionX * this.baseData.widthPx
                  item.y = item.positionY * this.baseData.heightPx
                })
              }
            }
            this.updateAddBtnPositioin()
          },50)
        }
      },
      // 预览图片
      doPreviewImage(url, index) {
        if (!this.previewFullImage) return
        const images = this.lists.sort((l1, l2) => { return l1.index - l2.index}).map(item => item.data.url || item.data.path)
        uni.previewImage({
          urls: images,
          current: url,
          success: () => {
            this.$emit('on-preview', url, this.sortList(), this.index)
          },
          fail: () => {
            this.showToast('预览图片失败')
          }
        })
      },
      // 检查文件后缀是否合法
      checkFileExt(file) {
        // 是否为合法后缀
        let noArrowExt = false
        // 后缀名
        let fileExt = ''
        const reg = /.+\./
        
        // #ifdef H5
        fileExt = file.name.replace(reg, '').toLowerCase()
        // #endif
        // #ifndef H5
        fileExt = file.path.replace(reg, '').toLowerCase()
        // #endif
        noArrowExt = this.limitType.some(ext => {
          return ext.toLowerCase() === fileExt
        })
        if (!noArrowExt) this.showToast(`不支持${fileExt}格式的文件`)
        return noArrowExt
      },
      
      /********************* 拖拽处理 ********************/
      
      // 更新拖拽信息
      updateDragInfo() {
        this.baseData.widthPx = uni.upx2px(this.width)
        this.baseData.heightPx = uni.upx2px(this.height)
        
        // 获取可移动区域的信息，用于判断当前有多少列
        const query = uni.createSelectorQuery().in(this)
        query.select('.tn-image-upload__movable-area').boundingClientRect()
        query.exec((res) => {
          if (!res) {
            setTimeout(() => {
              this.updateDragInfo()
            }, 10)
            return
          }
          this.baseData.columns = Math.floor(res[0].width / this.baseData.widthPx)
          
          // 初始化可拖拽列表信息
          this.lists = []
          this.fileList.forEach((item) => {
            // 首先检查内部是否已经添加过这张图片，因为外部绑定了一个对象给fileList的话(对象引用)，进行修改外部fileList时，
            // 会触发watch，导致重新把原来的图片再次添加到this.lists
            // 数组的some方法意思是，只要数组元素有任意一个元素条件符合，就返回true，而另一个数组的every方法的意思是数组所有元素都符合条件才返回true
            let tmp = this.lists.map(value => {
              return value.data
            }).some(listVal => {
              return listVal.url === item.url
            })
            // 如果内部没有这张图片，则添加到内部
            !tmp && this.lists.push(this.handleDragListItem({
              url: item.url,
              error: false,
              progress: 100
            }))
          })
          
          // 更新添加按钮位置
          this.updateAddBtnPositioin()
        })
      },
      // 处理拖拽列表信息
      handleDragListItem(item) {
        const positionX = this.lists.length % this.baseData.columns
        const positionY = Math.floor(this.lists.length / this.baseData.columns)
        const x = positionX * this.baseData.widthPx
        const y = positionY * this.baseData.heightPx
        return {
          id: this.unique(),
          x,
          y,
          preX: x,
          preY: y,
          positionX,
          positionY,
          zIndex:1,
          disabled: true,
          opacity: 1,
          scale: 1,
          index: this.lists.length,
          offset: 0,
          moveEnd: false,
          moving: false,
          data: {
            ...item
          }
        }
      },
      // 生成元素唯一id
      unique(n = 6) {
        let id = ''
        for (let i = 0; i < n; i++) id += Math.floor(Math.random() * 10)
        return 'tn_' + new Date().getTime() + id
      },
      // 更新添加按钮位置
      updateAddBtnPositioin() {
        if (this.lists.length >= this.maxCount) return
        
        setTimeout(()=>{
          this.addBtn.x = (this.lists.length % this.baseData.columns) * this.baseData.widthPx
          this.addBtn.y = Math.floor(this.lists.length / this.baseData.columns) * this.baseData.heightPx
        },50);
      },
      // 获取排序后数据
      sortList() {
        const list = this.lists.slice()
        list.sort((l1, l2) => {
          return l1.index - l2.index
        })
        return list.map(item => {
          return item.data
        })
      },
      mouseEnterArea () {
        // #ifdef H5
        this.lists.forEach(item => {
          item.disabled = false
        })
        // #endif
      },
      mouseLeaveArea () {
        // #ifdef H5
        if (this.baseData.dragItem) {
          this.lists.forEach(item => {
            item.disabled = true
            item.zIndex = 1
            item.offset = 0
            item.moveEnd = true
            if (item.id === this.baseData.dragItem.id) {
              if (this.timer) {
                clearTimeout(this.timer)
                this.timer = null
              }
              item.x = item.preX
              item.y = item.preY
              this.$nextTick(() => {
                item.x = item.positionX * this.baseData.widthPx
                item.y = item.positionY * this.baseData.heightPx
                this.baseData.dragItem = null
              })
            }
          })
          this.dragging = false
        }
        // #endif
      },
      movableLongPress(item) {
        // #ifndef H5
          uni.vibrateShort()
          // console.log("LongPress--------------------------------------------------------------");
          this.lists.forEach(value => {
            value.moving = false
          })
          this.dragging = true
          // 设置对应的元素允许拖动
          const index = this.lists.findIndex(obj => {
            return obj.id === item.id
          })
          item.disabled = false
          item.opacity = 0.7
          item.scale = 1.1
          this.$set(this.lists, index, item)
        // #endif
      },
      movableChange (e, item) {
        if (!item || !this.dragging) return
        // console.log("movableChange");
        item.moving = true
        item.preX = e.detail.x
        item.preY = e.detail.y
        // console.log(item.preX, item.preY);
        
        if (e.detail.source === 'touch') {
          if (!item.moveEnd) {
            item.offset = Math.sqrt(
              Math.pow(item.preX - item.positionX * this.baseData.widthPx, 2) + 
              Math.pow(item.preY - item.positionY * this.baseData.heightPx, 2))
          }
          let x = Math.floor((e.detail.x + this.baseData.widthPx / 2) / this.baseData.widthPx)
          if (x > this.baseData.columns) return
          let y = Math.floor((e.detail.y + this.baseData.heightPx / 2) / this.baseData.heightPx)
          let index = this.baseData.columns * y + x
          if (item.index !== index && index < this.lists.length) {
            for (let obj of this.lists) {
              if (item.index > index && obj.index >= index && obj.index < item.index) {
                this.updateItemPosition(obj, 1)
              } else if (item.index < index && obj.index <= index && obj.index > item.index) {
                this.updateItemPosition(obj, -1)
              } else if (item.id != obj.id) {
                // obj.offset = 0
                // console.log(obj.moving);
                // if (!obj.moving) {
                //   obj.preX = obj.positionX * this.baseData.widthPx
                //   obj.preY = obj.positionY * this.baseData.heightPx
                //   console.log("moving", obj.id, obj.preX, obj.preY);
                // }
                // obj.x = obj.preX
                // obj.y = obj.preY
                // // console.log(obj.id, obj.preX, obj.preY);
                // this.$nextTick(() => {
                //   obj.x = obj.positionX * this.baseData.widthPx
                //   obj.y = obj.positionY * this.baseData.heightPx
                // })
              }
            }
            item.index = index
            item.positionX = x
            item.positionY = y
            // TODO 发送事件
          }
        }
      },
      movableStart (item) {
        // console.log("movableStart");
        this.lists.forEach(item => {
          item.zIndex = 1
          // #ifdef H5
          item.disabled = false
          // #endif
        })
        item.zIndex = 10
        item.moveEnd = false
        this.baseData.dragItem = item
        // #ifdef H5
        this.dragging =true
        this.timer = setTimeout(() => {
          item.opacity = 0.7
          item.scale = 1.1
          clearTimeout(this.timer)
          this.timer = null
        }, 200)
        // #endif
      },
      movableEnd (item) {
        if (!this.dragging) return
        // console.log("movableEnd");
        const index = this.lists.findIndex(obj => {
          return obj.id === item.id
        })
        if (!item.moving) {
          item.preX = item.positionX * this.baseData.widthPx
          item.preY = item.positionY * this.baseData.heightPx
        }
        item.x = item.preX
        item.y = item.preY
        item.offset = 0
        item.moveEnd = true
        item.moving = false
        item.disabled = true
        // console.log(item.x, item.y);
        // console.log(item.id, item.moving);
        // this.$set(this.lists, index, item)
        // this.lists[index] = item
        // console.log(this.lists[index]);
        this.lists.forEach(listValue => {
          listValue.moving = false
          listValue.disabled = true
        })
        this.$nextTick(() => {
          item.x = item.positionX * this.baseData.widthPx
          item.y = item.positionY * this.baseData.heightPx
          item.opacity = 1
          item.scale = 1
          this.baseData.dragItem = null
          this.dragging = false
          // console.log(item.x, item.y);
          this.$set(this.lists, index, item)
        })
        this.$emit('sort-list', this.sortList())
      },
      // 更新图片位置信息
      updateItemPosition(item, offset) {
        const index = this.lists.findIndex(obj => {
          return obj.id === item.id
        })
        item.index += offset
        item.offset = 0
        item.positionX = item.index % this.baseData.columns
        item.positionY = Math.floor(item.index / this.baseData.columns)
        if (!item.moving) {
          item.preX = item.positionX * this.baseData.widthPx
          item.preY = item.positionY * this.baseData.heightPx
        }
        item.x = item.preX
        item.y = item.preY
        // console.log("updateItemPosition", item.id, item.preX, item.preY);
        // this.$set(this.lists, index, item)
        this.$nextTick(() => {
          item.x = item.positionX * this.baseData.widthPx
          item.y = item.positionY * this.baseData.heightPx
          this.$set(this.lists, index, item)
        })
      }
    }
  }
</script>

<style lang="scss" scoped>
  
  .tn-image-upload {
    position: relative;
    
    &__movable-area {
      width: 100%;
    }
    
    &__movable-view {
      border-radius: 10rpx;
      overflow: hidden;
    }
    
    &__item {
      /* #ifndef APP-NVUE */
      display: flex;
      /* #endif */
      align-items: center;
      justify-content: center;
      width: 200rpx;
      height: 200rpx;
      background-color: transparent;
      position: relative;
      border-radius: 10rpx;
      overflow: hidden;
      
      &-preview {
        border: 1rpx solid $tn-border-solid-color;
        
        &__delete {
          display: flex;
          align-items: center;
          justify-content: center;
          position: absolute;
          top: 0;
          right: 0;
          z-index: 10;
          border-top: 60rpx solid;
          border-left: 60rpx solid transparent;
          border-top-color: $tn-color-red;
          width: 0rpx;
          height: 0rpx;
          
          &--icon {
            position: absolute;
            top: -50rpx;
            right: 6rpx;
            color: #FFFFFF;
            font-size: 24rpx;
            line-height: 1;
          }
        }
        
        &__progress {
          position: absolute;
          width: auto;
          bottom: 0rpx;
          left: 0rpx;
          right: 0rpx;
          z-index: 9;
          /* #ifdef MP-WEIXIN */
          display: inline-flex;
          /* #endif */
        }
        
        &__error-btn {
          position: absolute;
          bottom: 0;
          left: 0;
          right: 0;
          background-color: $tn-color-red;
          color: #FFFFFF;
          font-size: 20rpx;
          padding: 8rpx 0;
          text-align: center;
          z-index: 9;
          line-height: 1;
        }
        
        &__image {
          display: block;
          width: 100%;
          height: 100%;
          border-radius: 10rpx;
        }
      }
      
      &-add {
        flex-direction: column;
        color: $tn-content-color;
        font-size: 26rpx;
        
        &--icon {
          font-size: 40rpx;
        }
        
        &__tips {
          margin-top: 20rpx;
          line-height: 40rpx;
        }
      }
    }
    
    &__add {
      background-color: $tn-font-holder-color;
      position: absolute;
      // margin: 10rpx;
      // margin-left: 0;
    }
  }
</style>
